﻿using DeepEarth.Toolkit.Geometry;
using DeepEarth.Toolkit.Geometry.Data;
using DeepEarth.Core;
using System.Windows.Input;
using System.Windows;
using System;
using System.Windows.Media;
using System.Windows.Media.Effects;
using System.Windows.Controls;
using DeepEarth.Map.Core;
using System.ComponentModel;
using System.Linq;
using System.Windows.Data;
namespace DeepEarth.Toolkit.Drawing
{
    public class FreeDraw : BaseDrawProcessor
    {
        // use the linestring to draw a shape
        LineString lineString;

        // use the freeDraw to hold the location of the points actually added
        // this is what will be added to the geometrycollection when drawing is complete
        ShapeGeometry freeDrawModel;

        public ShapeGeometry FreeDrawModel
        {
            get
            {
                return freeDrawModel;
            }
            set
            {
                freeDrawModel = value;
                freeDrawModel.Stroke = Stroke;
                freeDrawModel.StrokeThickness = StrokeThickness;
                freeDrawModel.Fill = Fill;
            }
        }

        // the previewDrawModel is used to assist drawing. It will include one more point than
        // the points added by the user.
        ShapeGeometry previewDrawModel;
        public ShapeGeometry PreviewDrawModel
        {
            get
            {
                return previewDrawModel;
            }
            set
            {
                previewDrawModel = value;
                previewDrawModel.Stroke = Stroke;
                previewDrawModel.StrokeThickness = StrokeThickness;
                previewDrawModel.Fill = Fill;
            }
        }

        Location currentDrawLocation;

        const double pixelRadius = 5.0;

        bool DrawPolygon { get; set; }

        bool IsDrawing {get;set;}

        public bool CanResume
        {
            get
            {
                return (!IsDrawing && this.FreeDrawModel != null && this.FreeDrawModel.Locations.Count > 0);
            }
        }

        Brush stroke = new SolidColorBrush(Color.FromArgb(180, 255, 215, 0));
        public Brush Stroke
        {
            get { return stroke ;}
            set
            {
                if (stroke != value)
                {
                    stroke = value;

                    SolidColorBrush scb = stroke as SolidColorBrush;

                    if (scb != null)
                    {
                        EffectColor = scb.Color;
                    }
                }

                if (this.FreeDrawModel != null)
                {
                    this.FreeDrawModel.Stroke = value;
                }

                if (this.PreviewDrawModel != null)
                {
                    this.PreviewDrawModel.Stroke = value; ;
                }

                OnPropertyChanged("Stroke");
            }
        }

        double strokeThickness = 5;
        public double StrokeThickness
        {
            get { return strokeThickness; }
            set
            {
                if (strokeThickness != value)
                {
                    strokeThickness = value;

                    if (this.FreeDrawModel != null)
                    {
                        this.FreeDrawModel.StrokeThickness = value;
                    }

                    if (this.PreviewDrawModel != null)
                    {
                        this.PreviewDrawModel.StrokeThickness = value; ;
                    }
                    
                    OnPropertyChanged("StrokeThickness");
                }
            }
        }

        Brush fill = new SolidColorBrush(Color.FromArgb(100, 255, 215, 0));
        public Brush Fill
        {
            get { return fill; }
            set
            {
                if (fill != value)
                {
                    fill = value;

                    if (this.FreeDrawModel != null)
                    {
                        this.FreeDrawModel.Fill = value;
                    }

                    if (this.PreviewDrawModel != null)
                    {
                        this.PreviewDrawModel.Fill = value; ;
                    }
                }

                OnPropertyChanged("Fill");
            }
        }

        Color effectColor = Colors.Orange;
        public Color EffectColor
        {
            get { return effectColor; }
            set
            {
                effectColor = value;
            }
        }

        public FreeDraw(UIElement interactionSurface, Panel drawingSurface, Func<Location, Point> locationToViewportPoint, Func<Point, Location> viewportPointToLocation) 
            : base(interactionSurface, drawingSurface, locationToViewportPoint, viewportPointToLocation)
        {
            this.routedEventHandler = new MouseButtonEventHandler(InteractionSurface_MouseLeftButtonDown);
        }

        bool InLocationRadius(Location location, Point viewportPoint)
        {
            Point locationPoint = this.locationToViewportPoint(location);

            double difX = viewportPoint.X - locationPoint.X;
            double difY = viewportPoint.Y - locationPoint.Y;

            return Math.Sqrt(Math.Pow(difX, 2.0) + Math.Pow(difY, 2.0)) <= pixelRadius;
        }

        MouseButtonEventHandler routedEventHandler;

        public void BeginFreeDraw()
        {
            IsDrawing = false;
            this.FreeDrawModel = new LineStringGeometry();
            this.PreviewDrawModel = new LineStringGeometry();
            // The FreeDrawProcessor doesn't use the model to manipulate the linestring path. (The model may switch while drawing
            // so instead of keeping the model and path in sync, we just manipulate the path directly)

            this.lineString = new LineString();
            this.lineString.MaintainConstantSize = true;
            this.lineString.IsHitTestVisible = false;

            this.lineString.MaintainConstantSize = true;

            Binding strokeBinding = new Binding("Stroke") {Source=this, Mode=BindingMode.OneWay};
            BindingOperations.SetBinding(lineString, PathBase.StrokeProperty, strokeBinding);

            Binding strokeThicknessBinding = new Binding("StrokeThickness") { Source = this, Mode = BindingMode.OneWay }; ;
            BindingOperations.SetBinding(lineString, PathBase.StrokeThicknessProperty, strokeThicknessBinding);
            
            if (!DrawPolygon)
            {
                this.lineString.Fill = new SolidColorBrush(Colors.Transparent);
            }
            else
            {
                Binding fillBinding = new Binding("Fill") { Source = this, Mode = BindingMode.OneWay }; ;
                BindingOperations.SetBinding(lineString, PathBase.FillProperty, fillBinding);
            }

            this.lineString.Locations = this.PreviewDrawModel.Locations;

            this.FreeDrawModel.Locations.CollectionChanged += Locations_CollectionChanged;

            drawingSurface.Children.Add(lineString);

            ResumeFreeDraw();
        }

        public void ResumeFreeDraw()
        {
            if (!IsDrawing && this.FreeDrawModel != null)
            {
                SyncWithPreview();

                interactionSurface.AddHandler(UIElement.MouseLeftButtonDownEvent, routedEventHandler, true);
                interactionSurface.MouseMove += InteractionSurface_MouseMove;

                if (this.FreeDrawModel.Locations.Count > 0)
                {
                    this.currentDrawLocation = this.FreeDrawModel.Locations[this.FreeDrawModel.Locations.Count - 1].Clone();
                }
                else
                {
                    this.currentDrawLocation = new Location();
                }

                if (PreviewDrawModel.Locations.Count > 0)
                {
                    this.PreviewDrawModel.Locations.Add(currentDrawLocation);
                }

                IsDrawing = true;
            }
        }

        void SyncWithPreview()
        {
            // Often, external controls, such as the measure tool, or ShapeEditor, will bind to the previewDrawModel. In that case, we want to sync
            // the freeDrawModel to the previewDrawModel on a resume.
            if (this.FreeDrawModel != null && this.PreviewDrawModel != null && this.PreviewDrawModel.Locations.Any())
            {
                this.FreeDrawModel.Locations.Clear();

                foreach (Location l in this.PreviewDrawModel.Locations)
                {
                    this.FreeDrawModel.Locations.Add(l.Clone());
                }
            }
        }

        public void EndFreeDraw()
        {
            EndFreeDraw(false);
        }

        void EndFreeDraw(bool endedInternally)
        {
            // If the free draw model didn't end drawing (due to the user clicking on the start or the last point)
            // the model contains an extra location for drawing purposes. Remove it.
            // We have to remove based on location due to .Equals override in the Location class.
            // i.e. Can't just .Remove(currentDrawLocation)            
            bool match = false;
            int counter = 0;
            foreach (Location l in this.PreviewDrawModel.Locations)
            {
                if (object.ReferenceEquals(l, currentDrawLocation))
                {
                    match = true;
                    break;
                }
                counter++;
            }

            if (match)
            {
                this.PreviewDrawModel.Locations.RemoveAt(counter);
            }
            this.lineString.Effect = null;

            interactionSurface.RemoveHandler(UIElement.MouseLeftButtonDownEvent, routedEventHandler);
            interactionSurface.MouseMove -= InteractionSurface_MouseMove;

            // if we endedInternally, then fire the notification event
            if (endedInternally)
            {
                OnDrawComplete(this.FreeDrawModel.Clone());
            }

            IsDrawing = false;
        }

        public void Clear()
        {
            this.FreeDrawModel.Locations.Clear();
            this.PreviewDrawModel.Locations.Clear();
            drawingSurface.Children.Clear();
        }

        void Locations_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            EnsureCorrectModel();
        }

        void EnsureCorrectModel()
        {
            if (this.FreeDrawModel is LineStringGeometry && LocationsImplyPolygon())
            {
                this.FreeDrawModel.Locations.CollectionChanged -= Locations_CollectionChanged;

                PolygonGeometry polyGeo = new PolygonGeometry()
                {
                    Locations = this.FreeDrawModel.Locations
                };

                this.FreeDrawModel = polyGeo;

                polyGeo.Locations.CollectionChanged += Locations_CollectionChanged;
            }
            else
            {
                if (!LocationsImplyPolygon())
                {
                    this.FreeDrawModel.Locations.CollectionChanged -= Locations_CollectionChanged;

                    LineStringGeometry lineString = new LineStringGeometry()
                    {
                        Locations = this.FreeDrawModel.Locations
                    };

                    this.FreeDrawModel = lineString;

                    lineString.Locations.CollectionChanged += Locations_CollectionChanged;
                }
            }

        }

        bool LocationsImplyPolygon()
        {
            return freeDrawModel.Locations.Count >= 3 && (freeDrawModel.Locations[0] == freeDrawModel.Locations[freeDrawModel.Locations.Count - 1]);
        }

        void InteractionSurface_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            e.Handled = true;

            if (this.FreeDrawModel.Locations.Count > 1)
            {
                Point currentMousePosition = e.GetPosition(drawingSurface);

                // user has clicked on the last location that they added, complete the line
                if (MouseOverLast(currentMousePosition))
                {
                    EndFreeDraw(true);
                    return;
                }

                // user has closed the shape - draw a polygon
                if (this.FreeDrawModel.Locations.Count > 2 && MouseOverStart(currentMousePosition))
                {
                    this.FreeDrawModel.Locations.Add(this.FreeDrawModel.Locations[0].Clone());
                    this.PreviewDrawModel.Locations.Add(this.PreviewDrawModel.Locations[0].Clone());

                    EnsureCorrectModel();

                    EndFreeDraw(true);
                    return;
                }

            }

            // continue drawing
            SyncMouseLocation(e.GetPosition(drawingSurface));

            // add the first point if there is none
            // we want the model to have at least two locations so we can see it dynamically redraw when the user moves the mouse
            if (previewDrawModel.Locations.Count == 0)
            {
                this.PreviewDrawModel.Locations.Add(currentDrawLocation);
            }

            var clone = currentDrawLocation.Clone();

            this.FreeDrawModel.Locations.Add(clone);

            // ensure that the currentDrawLocation instance is the last location in the previewDrawModel by inserting
            this.PreviewDrawModel.Locations.Insert(this.PreviewDrawModel.Locations.Count - 1, clone);

            Refresh();
        }

        bool MouseOverStart(Point currentMousePosition)
        {
            return InLocationRadius(this.FreeDrawModel.Locations[0], currentMousePosition);
        }

        bool MouseOverLast(Point currentMousePosition)
        {
            if (this.FreeDrawModel.Locations.Count > 1)
            {
                return InLocationRadius(this.FreeDrawModel.Locations[this.FreeDrawModel.Locations.Count - 1], currentMousePosition);
            }
            else
            {
                return false;
            }
        }

        Effect finishShapeEffect;
        Effect FinishShapeEffect
        {
            get
            {
                if (finishShapeEffect == null)
                {
                    finishShapeEffect = new DropShadowEffect
                    {
                        BlurRadius = 200,
                        Color = EffectColor,
                        ShadowDepth = 0
                    };
                }
                return finishShapeEffect;
            }
        }

        void SyncMouseLocation(Point mousePoint)
        {
            Location mouseLocation = viewportPointToLocation(mousePoint);
            this.currentDrawLocation.Latitude = mouseLocation.Latitude;
            this.currentDrawLocation.Longitude = mouseLocation.Longitude;
        }

        void InteractionSurface_MouseMove(object sender, MouseEventArgs e)
        {
            Point mousePoint = e.GetPosition(drawingSurface);
            SyncMouseLocation(mousePoint);

            this.lineString.Effect = null;

            if (this.FreeDrawModel.Locations.Count > 1 && MouseOverLast(mousePoint))
            {
                this.lineString.Effect = FinishShapeEffect;
            }

            if (this.FreeDrawModel.Locations.Count > 2 && MouseOverStart(mousePoint))
            {
                this.lineString.Effect = FinishShapeEffect;
            }

            Refresh();
        }

        public void Refresh()
        {
            this.lineString.Refresh(new Point(0,0), 16848);
        }   
    }
}
